//
// Author: Vladimir Migashko <migashko@gmail.com>, (C) 2011
//
// Copyright: See COPYING file that comes with this distribution
//
#ifndef HITLIST_HIT_CONTAINER_HPP
#define HITLIST_HIT_CONTAINER_HPP

#include "hit.hpp"
#include "hit_item.hpp"
#include "user_hits.hpp"
#include "tags.hpp"
#include <list>
#include <map>
#include <deque>
#include <vector>
#include <algorithm>
#include <iostream>

/** @brief Контейнер хитов по всем пользователям.
  */
class hit_container
{
  typedef std::map< user_id_t, user_hits > users_map;
public:
  hit_container()
    : _hit_interval(60)
    , _prepared_timelife(60*10)
    , _days_in_month(30)
    , _idle_timeout(60*10)
    , _idle_count(1000)
    , _idle_start(0)
    , _idle_user_id(-1)
  {
  }

  /** @brief Начальная инициализация
   * @param hit_interval максимальный интервал времени между хитами в которм хит считается за один (сек)
   * @param prepared_timelife время жизни кэша уникальных хитов (сек)
   * @param days_in_month дней в месяце (сколько дней хранить сообщение)
   * @param idle_timeout интервал запуска процедуры очистки устаревших хитов (сек)
   * @param idle_count количество юзеров у которых чистятся хиты за одну итерацию (сек)
    */
  void initialize(time_t hit_interval, time_t prepared_timelife, time_t days_in_month, time_t idle_timeout, size_t idle_count)
  {
    _hit_interval = hit_interval;
    _prepared_timelife = prepared_timelife;
    _days_in_month = days_in_month;
    _idle_timeout = idle_timeout;
    _idle_count = idle_count;
  }

  /** @brief Удалить все */
  void clear()
  {
    _users.clear();
  }


  /** @return общее количиство хитов для всех ползователей*/
  size_t hits_size() const 
  {
    size_t result = 0;
    users_map::const_iterator itr = _users.begin();
    for ( ;itr!=_users.end(); ++itr)
      result += itr->second.hits_month();
    return result;
  }

  /** @return количиство уникальных хитов для всех ползователей
   * Ахтунг! для каждого пользователя будет создан кэш уникальных просмотров!
   */
  size_t hosts_size() const 
  {
    size_t result = 0;
    users_map::const_iterator itr = _users.begin();
    for ( ;itr!=_users.end(); ++itr)
      result += itr->second.hosts_month();
    return result;
  }

  /** @brief Добавить хит для пользователя
    * @param first_id Идентификатор "кого смотрели"
    * @param second_id Идентификатор "кто смотрел"
    * @param hit_time Время простмотра unix timespan
    * @see hit
    */
  bool hit(user_id_t first_id,  user_id_t  second_id, time_t hit_time)
  {
    users_map::iterator itr = _users.find(first_id);
    if ( itr == _users.end() )
      itr = _users.insert( std::make_pair(first_id, user_hits(_hit_interval, _prepared_timelife, _days_in_month) ) ).first;
    if ( itr == _users.end() )
      return false;
    return itr->second.hit(second_id, hit_time);
  }

  /** @see user_hits::hits_month*/
  size_t hits_month(user_id_t first_id) const
  {
    users_map::const_iterator itr = _users.find(first_id);
    if ( itr == _users.end() )
      return 0;
    return itr->second.hits_month();
  }

  /** @see user_hits::hosts_month*/
  size_t hosts_month(user_id_t first_id) const
  {
    users_map::const_iterator itr = _users.find(first_id);
    if ( itr == _users.end() )
      return 0;
    return itr->second.hosts_month();
  }

  /** @see user_hits::hits_day*/
  size_t hits_day(user_id_t first_id) const
  {
    users_map::const_iterator itr = _users.find(first_id);
    if ( itr == _users.end() )
      return 0;
    return itr->second.hits_day();
  }

  /** @see user_hits::hosts_day*/
  size_t hosts_day(user_id_t first_id) const
  {
    users_map::const_iterator itr = _users.find(first_id);
    if ( itr == _users.end() )
      return 0;
    return itr->second.hosts_day();
  }

  /** @see user_hits::day_hitlist*/
  void day_hitlist( user_id_t first_id, size_t offset, size_t limit, std::vector<hit_item>& hl ) const
  {
    hl.clear();
    users_map::const_iterator itr = _users.find(first_id);
    if ( itr == _users.end() )
      return;
    itr->second.day_hitlist(offset, limit, hl );
  }

  /** @see user_hits::month_hitlist*/
  void month_hitlist( user_id_t first_id, size_t offset, size_t limit, std::vector<hit_item>& hl ) const
  {
    hl.clear();
    users_map::const_iterator itr = _users.find(first_id);
    if ( itr == _users.end() )
      return;
    itr->second.month_hitlist(offset, limit, hl );
  }

  /** Раз в _idle_timeout начинает процедуру удаления старых хитов.
   * При каждом последующем вызове обходит _idle_count пользователей за раз
    */
  std::pair<size_t, size_t> idle()
  {
    std::pair<size_t, size_t> result;
    time_t now = time(0);
    if ( _idle_user_id == -1 && (_idle_start + _idle_timeout) < now )
    {
      /// Ок. Начинаем процедуру удаления 
      _idle_start = now;
      if ( !_users.empty() )
        _idle_user_id = _users.begin()->first;
    }

    if ( _idle_user_id != -1)
    {
      users_map::iterator itr = _users.lower_bound(_idle_user_id);
      for ( size_t i = 0; itr!=_users.end() && i < _idle_count; ++i)
      {
        if (size_t count = itr->second.remove_old() )
        {
          result.first++;
          result.second+=count;
          if (itr->second.empty())
          {
            _users.erase(itr++);
            continue;
          }
        }
        ++itr;
      }
      if ( itr==_users.end() )
      {
        /// Закончили обход
        _idle_user_id = -1;
      }
      else
        _idle_user_id = itr->first;
    }
    return result;
  }

private:

private:
  // интервал времени в секундах в промежуток между которыми два хита считаются одним
  time_t _hit_interval;
  time_t _prepared_timelife;
  time_t _days_in_month;
  time_t _idle_timeout;
  size_t _idle_count;
  time_t _idle_start;
  user_id_t _idle_user_id;
  users_map _users;
};

#endif

